using System;

namespace Neural
{
	/// <summary>
	/// Layer of neurons
	/// </summary>
	public class Layer			
	{		
		
		#region fields

		/**************************************************************/
		/*                      Fields
		/**************************************************************/

		/// <summary>
		/// Output vector of this layer
		/// </summary>
		public readonly double[] Outputs;

		/// <summary>
		/// Array of neurons in this layer
		/// </summary>
		public readonly Neuron[] Neurons;

		#endregion

		#region properties

		/**************************************************************/
		/*                      Properties
		/**************************************************************/

		/// <summary>
		/// Activation funtion for this layer
		/// </summary>
		public IActivation Activation
		{
			set
			{
				for (int i = 0; i < Neurons.Length; i++)
					Neurons[i].Activation = value;
			}
		}

		#endregion

		#region constructors

		/**************************************************************/
		/*                      Constructors
		/**************************************************************/

		public Layer(int inputs, int outputs, IActivation activation)
		{
			this.Outputs = new double[outputs];
			this.Neurons = new Neuron[outputs];
			for (int i = 0; i < outputs; i++)
				Neurons[i] = new Neuron(inputs, activation);
		}

		#endregion

		#region methods

		/**************************************************************/
		/*                      Methods
		/**************************************************************/
		
		/// <summary>
		/// Fires every neuron in this layer
		/// </summary>
		public void Propagate(double[] inputs)
		{
			for (int i = 0; i < Neurons.Length; i++)
				Outputs[i] = Neurons[i].Calculate(inputs);
		}

		/// <summary>
		/// Calculates deltas for every neuron in this layer (Used during back-propagation training)
		/// </summary>
		public void CalcDeltas(double[] targets, Layer upperLayer)
		{
			Neuron neuron;
			/*
			 * For output layer, delta is a difference between target value and
			 * neuron's output value
			 */
			if (upperLayer == null)
			{
				if (targets.Length != Neurons.Length)
					throw new ArgumentException("Output layer size does not match target vector size");
				
				for (int i = 0; i < Neurons.Length; i++)
				{
					neuron = Neurons[i];
					neuron.Delta = (targets[i] - neuron.Output) * neuron.Prime;
				}
			}
			/*
			 * For hidden layers, delta is a weighted sum of deltas
			 * of the uppper layer
			 */
			else
			{
				for (int i = 0; i < Neurons.Length; i++)
				{
					neuron = Neurons[i];

					double delta = 0;
					for (int j = 0; j < upperLayer.Neurons.Length; j++)
						delta += upperLayer.Neurons[j].GetWeightedDalta(i);
					
					neuron.Delta = delta * neuron.Prime;
				}
			}
		}

		/// <summary>
		/// Updates all weights in this layer
		/// </summary>
		public void UpdateWeights(double[] inputs, double rate, 
			double momentum)
		{
			for (int i = 0; i < Neurons.Length; i++)
				Neurons[i].UpdateWeights(inputs, rate, momentum);
		}

		/// <summary>
		/// Collects all weight changes during current epoch
		/// </summary>
		public void CollectWeights(double[] inputs, double rate)
		{
			for (int i = 0; i < Neurons.Length; i++)
				Neurons[i].CollectWeights(inputs, rate);
		}

		/// <summary>
		/// Prepares all neurons for new epoch
		/// </summary>
		public void ResetWeights()
		{
			for (int i = 0; i < Neurons.Length; i++)
				Neurons[i].ResetWeights();
		}

		/// <summary>
		/// Ends current epoch and applies previously 
		/// collected weight changes
		/// </summary>
		public void ApplyWeights(double rate)
		{
			for (int i = 0; i < Neurons.Length; i++)
				Neurons[i].ApplyWeights(rate);
		}

		/// <summary>
		/// Randomizes all weights in this layer
		/// </summary>
		public void Randomize(System.Random engine)
		{
			for (int i = 0; i < Neurons.Length; i++)
				Neurons[i].Randomize(engine);
		}

		#endregion

	}
}
